package com.credithc.signature.core.service;

import com.alibaba.fastjson.JSONObject;
import com.credithc.signature.common.mapper.ChannelMapper;
import com.credithc.signature.common.mapper.ProductSignMapper;
import com.credithc.signature.common.mapper.SignatureLogMapper;
import com.credithc.signature.common.po.*;
import com.credithc.signature.common.ro.NewSignProviderReq;
import com.credithc.signature.common.ro.OldSignProviderReq;
import com.credithc.signature.common.ro.callback.CallbackReq;
import com.credithc.signature.common.ro.core.CoreRequet;
import com.credithc.signature.common.service.SealInfoService;
import com.credithc.signature.common.service.SignatureLogService;
import com.credithc.signature.common.service.TemplateService;
import com.credithc.signature.common.utils.DateUtil;
import com.credithc.signature.common.utils.HttpUploadUtil;
import com.credithc.signature.common.utils.MessageFilterUtil;
import com.credithc.signature.common.utils.UUIDUtil;
import com.credithc.signature.core.configure.rabbitmq.CoreRabbitConfig;
import com.credithc.signature.core.constant.Constant;
import com.credithc.signature.core.rabbitmq.CoreRabbitSender;
import com.credithc.signature.core.service.signprovide.SignatureProvider;
import com.credithc.signature.core.service.signprovide.fadada.FadadaProvider;
import com.credithc.signature.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 描述：签章服务的核心处理逻辑<br/>
 * 作者：sunyang
 * 创建：2019/6/4 15:34
 * 版本：v1.0.0<br/>
 */
@Slf4j
@Service
public class CoreHandler implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Autowired
    private GeneratePDFService generatePdfService;

    @Autowired
    private UploadPDFService uploadPDFService;

    @Autowired
    private SignatureService signatureService;

    @Value("${signature.signatureTryTimes:3}")
    private Integer signatureTryTimes;

    @Value("${signature.uploadTryTimes:3}")
    private Integer uploadTryTimes;

    @Value("${signature.fileServer.http.downloadUrl}")
    private String downloadUrl;

    @Value("${signature.snapNoSignPdf.path}")
    private String snapNoSignPdf;

    @Value("${signature.snapSignPdf.path}")
    private String snapSignPdf;

    @Autowired
    private SignatureLogService signatureLogService;

    @Autowired
    private SealInfoService sealInfoService;

    @Autowired
    private TemplateService templateService;

    @Autowired
    CoreRabbitConfig coreRabbitConfig;

    @Autowired
    FadadaProvider fadadaProvider;

    @Autowired
    private CoreHandler coreHandler;

    /**
     * 核心处理逻辑：
     * 1.序列号消息
     * 2.生成PDF
     * 3.签章
     * 4.上传到文件服务器
     *
     * @param coreRequet
     * @return true处理成功
     */
    public void handle(CoreRequet coreRequet) {
        try {
            // 1.序列化消息
            List<Map<String, String>> certInfos = new ArrayList<>();
            Map<String, List<Map<String, Object>>> listData = new HashMap<>(16);
            Map<String, String> otherData = new HashMap<>(16);
            List<String> sealCodes = null;
            sealCodes = decodeJsonBody(coreRequet.getJsonBody(), coreRequet.getAreNewSignatrue(), certInfos, listData, otherData, sealCodes);
            log.info("－消息－: requestSn={},序列化消息完成", coreRequet.getRequestSn());

            // 2.生成PDF
            String pdfPath = coreHandler.generatePdf(coreRequet, listData, otherData, sealCodes);

            // 3.签章
            pdfPath = signatureAndResumption(coreRequet, certInfos, pdfPath, sealCodes);

            // 4.上传PDF
            pdfPath = uploadPDFAndResumption(coreRequet, pdfPath);

            // 5.通知回调
            storageToMQ(coreRequet.getTemplateNo(), coreRequet.getRequestSn(), true, coreRequet.getCallbackUrl(), pdfPath, "处理成功");

            // 6.更新记录状态
            updateSignLog(coreRequet.getRequestSn(), true);
        } catch (Throwable e) {
            String message = e.getCause().getLocalizedMessage();
            log.error("－消息－: requestSn={},签章失败,错误信息如下:{}", coreRequet.getRequestSn(), message);
            updateSignLog(coreRequet.getRequestSn(), false);
            // 回调业务方失败消息
            callbackBusiness(coreRequet, e);
        }
    }

    /**
     * 回调业务方
     *
     * @param coreRequet
     * @param e
     */
    private void callbackBusiness(CoreRequet coreRequet, Throwable e) {
        try {
            storageToMQ(coreRequet.getTemplateNo(), coreRequet.getRequestSn(), false, coreRequet.getCallbackUrl(), "", e.getMessage());
        } catch (Throwable throwable) {
            log.error("－消息－: requestSn={},回调发送到MQ失败", coreRequet.getRequestSn(), throwable);
        }
    }

    /**
     * 文件上传操作
     *
     * @param coreRequet
     * @param pdfPath
     * @return
     * @throws Exception
     */
    private String uploadPDFAndResumption(CoreRequet coreRequet, String pdfPath) throws Exception {
        if (StringUtils.isEmpty(pdfPath)) {
            return null;
        }
        long startUpload = System.currentTimeMillis();
        for (int uploadTryCount = 0; uploadTryCount <= uploadTryTimes; uploadTryCount++) {
            try {
                pdfPath = uploadPDFService.uploadPDF(pdfPath, coreRequet);
                break;
            } catch (Exception e) {
                if (uploadTryCount >= uploadTryTimes) {
                    long endUpload = System.currentTimeMillis();
                    log.error("－消息－: requestSn={},上传PDF耗时:{}毫秒,重试次数:{}", coreRequet.getRequestSn(), (endUpload - startUpload), uploadTryCount);
                    throw new RuntimeException(e);
                }
            }
        }
        long endUpload = System.currentTimeMillis();
        log.info("－消息－: requestSn={},上传PDF成功,耗时:{}毫秒", coreRequet.getRequestSn(), (endUpload - startUpload));
        return pdfPath;
    }

    /**
     * 查询签章三方，并执行签章逻辑
     *
     * @param coreRequet
     * @param certInfos
     * @param pdfPath
     * @param sealCodes
     * @return
     * @throws Exception
     */
    private String signatureAndResumption(CoreRequet coreRequet, List<Map<String, String>> certInfos, String pdfPath, List<String> sealCodes) throws Exception {
        if (coreRequet.getNeedSign()) {
            long startSign = System.currentTimeMillis();
            String requestSn = coreRequet.getRequestSn();
            for (int signatureTryCount = 0; signatureTryCount <= signatureTryTimes; signatureTryCount++) {
                try {
                    // 签章逻辑
                    pdfPath = sign(coreRequet, pdfPath, certInfos, sealCodes);
                    break;
                } catch (Exception e) {
                    if (signatureTryCount >= signatureTryTimes) {
                        throw new RuntimeException(e);
                    }
                }
            }
            long endSign = System.currentTimeMillis();
            log.info("－消息－: requestSn={},签章总耗时:{}毫秒", coreRequet.getRequestSn(), (endSign - startSign));
        }
        return pdfPath;
    }

    /**
     * 根据配比确定调用哪个三方 并签章
     *
     * @param coreRequet
     * @param pdfPath
     * @param certInfos
     * @param sealCodes
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Autowired
    private ProductSignMapper productSignMapper;

    @Autowired
    private SignatureLogMapper signatureLogMapper;

    @Autowired
    private ChannelMapper channelMapper;


    public String sign(CoreRequet coreRequet, String pdfPath, List<Map<String, String>> certInfos, List<String> sealCodes) throws Exception {
        Channel channel;
        Example example = new Example(Channel.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("channelMappingNo", coreRequet.getChannelNo());
        channel = channelMapper.selectOneByExample(example);
        if (channel == null) {
            channel = channelMapper.selectByChannelNo(coreRequet.getChannelNo());
        }

        Example pse = new Example(ProductSign.class);
        Example.Criteria psec = pse.createCriteria();
        psec.andEqualTo("productCode", channel.getChannelNo());
        List<ProductSign> productSigns = productSignMapper.selectByExample(pse);
        //未配置默认中金
        if (productSigns == null || productSigns.isEmpty()) {
            log.debug("－消息－: requestSn={},未配置路由,默认【中金】签章", coreRequet.getRequestSn());
            SignatureProvider provider = BeanUtil.getBean("zjSignature", SignatureProvider.class);
            return provider.processSignature(coreRequet, pdfPath, certInfos, sealCodes);
        }
        long vernier = System.currentTimeMillis() % 100;
        int vs = 0;
        for (ProductSign ps : productSigns) {
            vs += ps.getPercentage();
            if (vernier < vs) {
                log.debug("－消息－: requestSn={},根据配比执行【{}】签章", coreRequet.getRequestSn(), ps.getThirdPartyName());
                SignatureProvider provider = BeanUtil.getBean(ps.getMethodName(), SignatureProvider.class);
                return provider.processSignature(coreRequet, pdfPath, certInfos, sealCodes);
            }
        }
        return null;
    }

    public String generatePdf(CoreRequet coreRequet, Map<String, List<Map<String, Object>>> listData, Map<String, String> otherData, List<String> sealCodes) throws Exception {
        try {
            //1.检查是否需要分开签：就是已经签完的合同，想再次签章
            NewSignProviderReq newSignProviderReq = JSONObject.parseObject(coreRequet.getJsonBody(), NewSignProviderReq.class);
            if (newSignProviderReq.getSeparateSign() != null) {
                Map<String, String> separateMap = newSignProviderReq.getSeparateSign();
                String contractUri = separateMap.get("contractUri");
                String sealCode = separateMap.get("sealCode");
                String keyWord = separateMap.get("keyWord");
                //a.下载合同
                String snapPDFPath = PathUtil.getSnapPDFPath(snapNoSignPdf, coreRequet.getRequestSn());
                HttpUploadUtil.download(downloadUrl, contractUri, snapPDFPath);

                //b.添加公章关键字
                SealInfo sealInfo = sealInfoService.selectBySealCode(sealCode);
                String addWords = sealInfo.getKeyWords();
                sealCodes = new ArrayList<>();
                sealCodes.add(sealCode);

                String uuid = UUIDUtil.getUUID();
                String destFileName = Constant.SNAP_PDF_PREFIX + uuid + Constant.PDF_EXT;
                String date = DateUtil.getStandardDateStr();
                String signSnapPDFPath = PathUtil.getSignSnapPDFPath(snapSignPdf, destFileName, date);

                PdfStampUtil.findKeyWordAndAddWords(snapPDFPath, signSnapPDFPath, keyWord, addWords);
                //刪除之前的合同文件
                uploadPDFService.removeUnusedFile(snapPDFPath);
                return signSnapPDFPath;
            } else {
                //正常生成合同
                long start = System.currentTimeMillis();
                String noSignPath = generatePdfService.generatePDFToDisk(coreRequet, listData, otherData);
                long end = System.currentTimeMillis();
                log.info("－消息－: requestSn={},生成PDF成功,耗时:{}毫秒", coreRequet.getRequestSn(), (end - start));
                return noSignPath;
            }
        } catch (Exception e) {
            String errorMsg = MessageFilterUtil.limitLength(e.getMessage());
            signatureLogService.updateResult(errorMsg, coreRequet.getRequestSn());
            log.error("－消息－: requestSn={},生成PDF失败,", coreRequet.getRequestSn(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 更新签章状态
     *
     * @param requetSN
     * @param isSignSuccess
     */
    private void updateSignLog(String requetSN, boolean isSignSuccess) {
        SignatureLog signatureLog = new SignatureLog();
        signatureLog.setRequestSn(requetSN);
        if (isSignSuccess) {
            // 设置签章成功
            signatureLog.setSignStatus(2);
        } else {
            // 设置签章失败
            signatureLog.setSignStatus(3);
        }
        signatureLogService.updateByReqSNSelective(signatureLog);
    }

    /**
     * 将回调消息发送到MQ
     *
     * @param templateNo
     * @param requestSN
     * @param isSuccess
     * @param callBackURL
     * @param contractURI
     * @param errorMsg
     * @return
     */
    private boolean storageToMQ(String templateNo, String requestSN, boolean isSuccess, String callBackURL, String contractURI, String errorMsg) {
        CoreRabbitSender coreRabbitSender = (CoreRabbitSender) applicationContext.getBean(Constant.RABBITSENDER_NAME);
        Template template = templateService.selectByTemplateNo(templateNo);
        CallbackReq callbackReq = new CallbackReq(requestSN, isSuccess, callBackURL, contractURI, errorMsg, 0);
//        byte[] callbackBytes = ProtobufUtil.serializer(callbackReq);
        return coreRabbitSender.sendSync(callbackReq, template.getVip());
    }

    /**
     * 解包jsonBody
     *
     * @param jsonBody
     * @param isNewSignature
     * @param certInfos
     * @param listData
     * @param otherData
     */
    private List<String> decodeJsonBody(String jsonBody, boolean isNewSignature, List<Map<String, String>> certInfos,
                                        Map<String, List<Map<String, Object>>> listData, Map<String, String> otherData, List<String> sealCodes) {

        if (isNewSignature) {
            NewSignProviderReq newSignProviderReq = JSONObject.parseObject(jsonBody, NewSignProviderReq.class);
            if (newSignProviderReq.getCertInfos() != null) {
                certInfos.addAll(newSignProviderReq.getCertInfos());
            }
            if (newSignProviderReq.getListData() != null) {
                listData.putAll(newSignProviderReq.getListData());
            }
            if (newSignProviderReq.getOtherData() != null) {
                otherData.putAll(newSignProviderReq.getOtherData());
            }
            if (newSignProviderReq.getOfficialSealsCodes() != null) {
                sealCodes = new ArrayList<>();
                sealCodes.addAll(newSignProviderReq.getOfficialSealsCodes());

            }
        } else {
            OldSignProviderReq oldSignProviderReq = JSONObject.parseObject(jsonBody, OldSignProviderReq.class);
            if (oldSignProviderReq.getCertApplys() != null) {
                certInfos.addAll(oldSignProviderReq.getCertApplys());
            }
            if (oldSignProviderReq.getListElements() != null) {
                listData.putAll(oldSignProviderReq.getListElements());
            }
            if (oldSignProviderReq.getSingnalElement() != null) {
                otherData.putAll(oldSignProviderReq.getSingnalElement());
            }
            if (oldSignProviderReq.getOfficialSealsCodes() != null) {
                sealCodes = new ArrayList<>();
                sealCodes.addAll(oldSignProviderReq.getOfficialSealsCodes());
            }
        }
        // 实际中发现有的系统传idCardTypeCode，有的系统传idTypeCode，需要做非空判断
        certInfos.stream().forEach(cert -> {
            String idCardTypeCode = cert.get("idCardTypeCode");
            if (StringUtils.isNotBlank(idCardTypeCode)) {
                cert.put("idTypeCode", idCardTypeCode);
            } else {
                String idTypeCode = cert.get("idTypeCode");
                if (StringUtils.isBlank(idTypeCode)) {
                    cert.put("idTypeCode", Constant.MAINLAND_ID);
                }
            }
        });
        return sealCodes;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    // TODO
    public String synchronousSign(CoreRequet coreRequet) throws Exception {
        // 1.序列化消息
        List<Map<String, String>> certInfos = new ArrayList<>();
        Map<String, List<Map<String, Object>>> listData = new HashMap<>(16);
        Map<String, String> otherData = new HashMap<>(16);
        List<String> sealCodes = null;
        sealCodes = decodeJsonBody(coreRequet.getJsonBody(), coreRequet.getAreNewSignatrue(), certInfos, listData, otherData, sealCodes);

        // 2.生成PDF
        String pdfPath = generatePdf(coreRequet, listData, otherData, sealCodes);

        return fadadaProvider.synchronousSign(coreRequet, pdfPath, certInfos, sealCodes);
        //返回合同号
    }
}
